/**
 * Created by sunjia on 2017/04/05.
 */
package server;

import DM.DialogManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializationFeature;
import config.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import entities.SLUResult;
import entities.StateMachineModel;
import entities.SysAction;
import sluentities.*;
import gate.*;
import gate.Factory;
import gate.util.GateException;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
//import org.apache.log4j.LogManager;
import org.mongodb.morphia.Datastore;
//import org.slf4j.LoggerFactory;
//import org.slf4j.Logger;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import entities.Reply;

public class ParseServer extends HttpServlet {

    public String url;
    public static int poolSize;
    public BlockingQueue<Parser> parserPool;
    public int timeOut;

    private String mongodbIp;
    private String mongodbPort;
    private String databaseName;
    private String inputEncoding;
    private String outputEncoding;
    private DialogManager dialogManager;
    private String isUseContext;

    private static Logger logger = Logger.getLogger(ParseServer.class);
    private static boolean gateInited = false;

    private ReentrantReadWriteLock rwl;
    private Lock readLock;
    private Lock writeLock;
    private URL appURL;
    public static Datastore mongoDatastore;


    private void loadConfig(String path) {
        Properties property = new Properties();
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(path);
            property.load(inputStream);
            inputEncoding = property.getProperty("InputEncoding");
            outputEncoding = property.getProperty("OutputEncoding");
            poolSize = Integer.valueOf(property.getProperty("Nthread"));
            mongodbIp = property.getProperty("mongodbIp","localhost");
            mongodbPort = property.getProperty("mongodbPort","27017");
            databaseName = property.getProperty("databaseName","nxydNew");
            isUseContext = property.getProperty("isUseContext","false");
            url =  property.getProperty("DialogManagerURL","http://192.168.2.222:8080/tbi/csf_");
            timeOut =  Integer.valueOf(property.getProperty("Timeout", "5000"));
        } catch (Exception e) {
            System.err.println("Loading configuration failed, please check. Using default parameters.");
            System.exit(-1);
        }
        finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }


    public void init() throws ServletException {
        if (!gateInited) {
            try{
                rwl = new ReentrantReadWriteLock();
                readLock = rwl.readLock();
                writeLock = rwl.writeLock();
                //加载log日志配置文件log4j.properties
                PropertyConfigurator.configure(ParseServer.class.getClassLoader().getResource("log4j.properties"));
                loadConfig(ParseServer.class.getClassLoader().getResource("config.inf").getPath());
                //Gate初始化
                String home = ParseServer.class.getClassLoader().getResource("gate.xml").getPath();
                System.out.println("Gate home "+home);
                Gate.setGateHome(new File(home).getParentFile());
                Gate.init();
                Gate.getCreoleRegister().registerDirectories(ParseServer.class.getClassLoader().getResource("plugins/ANNIE"));
                //MongoDB初始化
                MongoDB.setDBConf(databaseName, mongodbIp, "", "", Integer.valueOf(mongodbPort));
                mongoDatastore = MongoDB.instance().getDatabase();

                parserPool = new LinkedBlockingQueue<Parser>();
                if(!gateInited){
                    try {
                        initInstance();
                        gateInited = true;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            catch(Exception e){
                logger.error("引擎初始化失败");
                e.printStackTrace();
            }
        }
    }

    public void initInstance() throws Exception {

        StateMachineModel smm = StateMachineModel.getModelFromInternelJson("plugins/nodeInfo.json");
        dialogManager = new DialogManager(600,true);//600s不运行则超时，重置状态机
        dialogManager.setStateMachineModel(smm);
        dialogManager.setConfig(isUseContext.equals("true") ? true : false, url,timeOut);

//        System.out.println("Preparing parser pool...");
        logger.info("Preparing parser pool...");
        appURL = ParseServer.class.getClassLoader().getResource("plugins/anguang.gapp");
        fillPool(appURL, parserPool);
//        System.out.println("Pools are ready now.");
        logger.info("Pools are ready now.");
    }

    private void fillPool(URL appURL, BlockingQueue<Parser> parserPool) throws Exception {
        for(int i = 0; i < poolSize; i++){
            Parser parser = new Parser(appURL);
            parser.setDialogManager(dialogManager);
            String path_corpus = ParseServer.class.getClassLoader().getResource("corpus").getPath();
            parser.processer.index(path_corpus);
            parserPool.add(parser);
        }
    }

    public void doGet (HttpServletRequest req, HttpServletResponse res){
        boolean keypadVaule = false;
        String input = null;
        Parser tempParser = null;
        try{
            req.setCharacterEncoding("ISO-8859-1");
            String greeting = req.getParameter("greeting");
            String modelUpdate = req.getParameter("modelUpdate");

            String userid = req.getParameter("userid");
            if(userid == null) userid = "";
            String tel = req.getParameter("telephoneNumber");
            if(tel == null) tel = "";
            String keypad = req.getParameter("keypad");
            if(keypad != null) keypadVaule = true;
            String query = req.getParameter("line");
            if(query == null) query = "";
            String debug = req.getParameter("debug");
            if(debug==null) debug = "false";
            String format = req.getParameter("format");
            if(format == null) format = "json";
            String format1 = format.trim().toLowerCase();
            if(format1.equals("json")){
                res.setContentType("text/Json; charset=" + "UTF-8");
            }
            if(format1.equals("xml")){
                res.setContentType("text/XML; charset=" + "UTF-8");
            }
            //if --> model更新；// else --> line,gate匹配
            if(modelUpdate != null && modelUpdate.equalsIgnoreCase("true")) {
                PrintWriter out = res.getWriter();
                updateModel(out);
                out.close();
            } else {
                    String offline = req.getParameter("offline");
                    if (offline != null && offline.equalsIgnoreCase("true")) {
                        dialogManager.resetState(userid);
                    }
                    query = new String(query.getBytes("ISO-8859-1"), inputEncoding);
                    query = query.replaceAll(" ", "").toLowerCase();
                    userid = new String(userid.getBytes("ISO-8859-1"), inputEncoding);
                    PrintWriter out = res.getWriter();
                    readLock.lock();
                    tempParser = parserPool.take();
                    String inputString = "";//用于在返回页面显示
                    if (greeting != null){//首问语
                        tempParser.greeting(userid);
                    }else {
                        if (keypadVaule==true){//进keypad流程，只需要Gate匹配
                            tempParser.keypadParser(keypad, userid, tel);
                            inputString = keypad;
                        }else {
                            tempParser.queryParser(query, userid, tel);
                            inputString = query;
                        }
                    }

                    SysAction action = tempParser.getCurrentAction();
                    ArrayList<SLUResult> sluResults = tempParser.getCurrentSLU();

                    ResultsEntity results = new ResultsEntity();
                    results.setReply(action.reply);
                    results.setUserid(userid);
                    results.setAction(action.stateID);
                    results.setParams(action.params);
                    results.setInput(inputString);
                    results.setFunction(action.query);
                    results.setKeypad(action.keypad);
//                    results.setFunc();
                    if(debug.trim().toLowerCase().equals("true")) {
                        results.setDebug(sluResults);
                        results.setGazetteer(tempParser.getCurrentGaz());

                    }
                    String resultStr = writeResult(results, format1);//format为输出格式Json或Xml

                    //Output
                    logger.info("userid:" + userid + "\tquery:" + query
                            + "\tAnswer:" + resultStr.replaceAll("\n", ""));
                    readLock.unlock();
                    out.print(resultStr);
                    out.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
            if(input != null)
                System.out.println(input);
        }finally{
            if(tempParser != null) parserPool.add(tempParser);
        }
    }

    private void updateModel(PrintWriter http_out){

        ModelUpdate update = new ModelUpdate();
        writeLock.lock();
        String strOut = "";
        try {
//            String path_Gaz = ModelUpdate.class.getClassLoader().getResource("plugins/gazetteer/").getPath();
            String path_corpus = ModelUpdate.class.getClassLoader().getResource("corpus/").getPath();
            String path_traindata = ModelUpdate.class.getClassLoader().getResource("traindata").getPath();
            try {
                // step1 更新语料
                update.DB2Corpus(path_corpus);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("更新失败，内部IO错误");
                return;
            }
            for(int i = 0; i < poolSize; i++){
                Parser parser = parserPool.take();
                cleanupSerialController(parser);
                parser.setAnalyser(null);
            }
            //step2 更新model
            try {
                update.classifier.train(path_corpus, path_traindata);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("更新失败，内部分类器错误");
            }
            String path_Json = ParseServer.class.getClassLoader().
                    getResource("plugins/nodeInfo.json/").getPath();
//            update.createJsonfromDB(path_Json);
            initInstance();
            strOut = "更新成功";
        } catch (Exception e) {
            e.printStackTrace();
            strOut = "更新失败，内部系统错误，请重启tomcat服务器";
        }
        finally{
            http_out.print("<results>" + strOut + "</results>");
            http_out.close();
            writeLock.unlock();
        }
    }


    private void cleanupSerialController(Parser parser) {

        for(Object aPr :new ArrayList(parser.getAnalyser().getPRs())){
            Factory.deleteResource((Resource)aPr);
        }
        Factory.deleteResource(parser.getAnalyser());
    }

    public String writeResult(ResultsEntity results, String resultType){
        String resultStr = "";
        try {
            switch (resultType) {
                case "xml":
                    resultStr = beanToXml(results, ResultsEntity.class);
                    break;
                case "json":
                    resultStr = entityToJson(results);
                    break;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return resultStr;
    }

    private String beanToXml(Object obj,Class<?> load) throws JAXBException {

        JAXBContext context = JAXBContext.newInstance(load);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        StringWriter writer = new StringWriter();
        marshaller.marshal(obj,writer);
        return writer.toString();
    }

    private String entityToJson(ResultsEntity results) {

        String json = "{}";
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
        try {
            json = mapper.writeValueAsString(results);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return json;
    }

}
